#include <iostream>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <cstring>
using namespace std;
/*
为了解决管道的半双工(消息单项通信)问题，我们使用System V来实现全双工(双向交流)

system V提供的通信方式
    a.消息队列
    b.共享内存
    c.信号量（信号灯集）

有关system V进程间通信对象相关的指令
ipcs:可以查看所有的信息（消息队列、共享内存、信号量）
ipcs -q:可以查看消息队列的信息
ipcs -m:可以查看共享内存的信息
ipcs -s:可以查看信号量的信息
ipcrm -q/m/s  ID :可以删除指定ID的IPC对象

system V三种进程通信方式也都借助于内核实现
与内核提供的管道、信号通信不同：system V的ipc对象实现了数据传递的容器与程序相分离，也
就是说，即使程序以己经结束，但是放入到容器中的数据依然存在，除非将容器手动删除


消息队列的注意事项：
    1、对于消息而言，由两部分组成：消息的类型和消息正文，消息结构体由用户自定义
    2、对于消息队列而言，任意一个进程都可以向消息队列中发送消息，也可以从消息队列中取消
    3、多个进程，使用相同的key值打开的是同一个消息队列
    4、对消息队列中的消息读取操作是一次性的，被读取后，消息队列中不存在该消息了
    5、消息队列的大小：16KB


消息队列的API:
1、创建key值
    #include <sys/types.h>
    #include <sys/ipc.h>

    key_t ftok(const char *pathname, int proj_id);    //ftok("/", 'k');
        功能：通过给定的文件以及给定的一个随机值，创建出一个4字节整数的key值，用于system V IPC对象的创建
        参数1：一个文件路径，要求是已经存在的文件路径，提供了key值3字节的内容，其中，文件的设备号占1字节，文件的inode号占2字节
        参数2：一个随机整数，取后8位（1字节）跟前面的文件共同组成key值，必须是非0的数字
        返回值：成功返回key值，失败返回-1并置位错误码


2、通过key值，创建消息队列
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>

    int msgget(key_t key, int msgflg);
        功能：通过给定的key值，创建出一个消息队列的对象，并返回消息队列的句柄ID，后期可以通过该ID操作整个消息队列
        参数1：key值，该值可以是IPC_PRIVATE,也可以是ftok创建出来的，前者只用于亲缘进程间的通信
        参数2：创建标识
            IPC_CREAT:创建并打开一个消息队列，如果消息队列已经存在，则直接打开
            IPC_EXCL:确保本次创建处理的是一个新的消息队列，如果消息队列已经存在，则报错，错误码位EEXIST
            0664：该消息队列的操作权限
            eg: IPC_CREAT|0664  或者  IPC_CREAT|IPC_EXCL|0664
    返回值：成功返回消息队列的ID号，失败返回-1并置位错误码


3、向消息队列中存放数据
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>

    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
        功能：向消息队列中存放一个指定格式的消息
        参数1：打开的消息队列的id号
        参数2：要发送的消息的起始地址，消息一般定义为一个结构体类型，由用户手动定义
        struct msgbuf {
               long mtype;       //must be > 0  消息的类型(不是说一定是正数0也可以是负数但会导致接受函数可能会出错)
               char mtext[1];   //消息正文
        };
        参数3：消息正文的大小
        参数4：是否阻塞的标识
            0：标识阻塞形式向消息队列中存放消息，如果消息队列满了，就在该函数处阻塞
            IPC_NOWAIT : 标识非阻塞的形式向消息队列中存放消息，如果消息队列满了，直接返回
        返回值：成功返回0，失败返回1并置位错误码


4、从消息队列中取消息 ssize_t
     msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
    功能：从消息队列中取数指定类型的消息放入给定的容器中(读取后就不存在了，消息队列中的消息会变少)
    参数1：打开的消息队列的id号
    参数2：要接收的消息的起始地址，消息一般定义为一个结构体类型，由用户手动定义
    struct msgbuf{
        long mtype; //message type, must be > 0 消息的类型
        char mtext[1]; // message data 消息正文
    };
    参数3：消息正文的大小
    参数4：要接收的消息类型
        0:表示每次都取消息队列中的第一个消息，无论类型
        >0:读取队列中第一个类型为msgtyp的消息
        <0:读取队列中的一个消息，消息为绝对值小于msgtyp的第一个消息(类型不为负原因在这，因为类型为负时，会从队列中绝对值小于msgtyp的类型的消息中选取第一个消息)
        eg： 10 --> 8 --> 3 --> 6 --> 5 --> -5 --> 2    -5 : 会从队列中绝对值小于5的类型的消息中选取第一个消息，就是3，不会是-5
        参数5是否阻塞的标识
            0标识阻塞形式向消息队列中读取消息，如果消息队列空了，就在该函数处阻塞
            IPC_NOWAIT:标识非阻塞的形式向消息队列中读取消息，如果消息队列空了，直接返回
        返回值：成功返回实际读取的正文大小，失败返回 - 1并置位错误码



5、销毁消息队列
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>

    int msgctl(int msqid, int cmd, struct msqid_ds *buf);
    功能：对给定的消息队列执行相关的操作，该操作由cmd参数而定
    参数1：消息队列的ID号
    参数2：要执行的操作
        IPC_RMID：删除一个消息队列，当cmd为该值时，第三个参数可以省略填NULL即可(标记为待删除，当引用数为0时候才会删除，详细见共享内存的同款解释)
        IPC_STAT：表示获取当前消息队列的属性，此时第三个参数就是存放获取的消息队列属性的容器起始地址
        IPC_SET：设置当前消息队列的属性，此时第三个参数就是要设置消息队列的属性数据的起 始地址
    参数3：消息队列数据容器结构体，如果第二个参数为IPC_RMID,则该参数忽略填NULL即可，如果是IPC_STAT、IPC_SET填如下结构体：
        struct msqid_ds {
            struct ipc_perm msg_perm;     // Ownership and permissions消息队列的拥有者和权限
               time_t          msg_stime;    // Time of last msgsnd(2)   最后一次发送消息的时间
               time_t          msg_rtime;    // Time of last msgrcv(2)    最后一次接收消息的时间
               time_t          msg_ctime;    // Time of last change       最后一次状态改变的时间
               unsigned long   __msg_cbytes; // Current number of bytes in queue(nonstandard) 已用字节数
               msgqnum_t       msg_qnum;     // Current number of messages inqueue  消息队列中消息个数
               msglen_t        msg_qbytes;   //Maximum number of bytes allowedin queue 最大消息个数
               pid_t           msg_lspid;    // PID of last msgsnd(2)     最后一次发送消息的进程pid
               pid_t           msg_lrpid;    // PID of last msgrcv(2)     最后一次读取消息的进程pid
           };
            该结构体的第一个成员类型如下：
            struct ipc_perm {
               key_t          __key;       // Key supplied to msgget(2)    key值
               uid_t          uid;         // Effective UID of owner   当前进程的uid
               gid_t          gid;         // Effective GID of owner    当前进程的组ID
               uid_t          cuid;        // Effective UID of creator  消息队列创建者的用户id
               gid_t          cgid;        // Effective GID of creator  消息队列创建者的组id
               unsigned short mode;        // Permissions             消息队列的权限
               unsigned short __seq;       // Sequence number           队列号
           };
    返回值：成功返回0，失败返回-1并置位错误码
*/

int main(int argc, const char *argv[])
{
    // 1.创建key值
    key_t key = ftok("./", 'k');
    if (key == -1)
    {
        perror("ftok error");
        return -1;
    }
    printf("key = %#x\n", key); // 16进制表示

    // 2.创建消息队列,并返回这个消息队列的ID号
    int msgid = msgget(key, IPC_CREAT | 0664);
    if (msgid == -1)
    {
        perror("msgget error");
        return -1;
    }
    printf("msgid = %d\n", msgid);

    // 3.发送消息
    struct msgbuf
    {
        long mtype;       // 消息的类型
        char mtext[1024]; // 消息正文
    };
    struct msgbuf msg;
    int msgsize = sizeof(struct msgbuf) - sizeof(long);
    while (1)
    {
        printf("请输入要发送的消息类型：");
        scanf("%ld", &msg.mtype);
        getchar(); // 吸收缓冲区中的换行符
        printf("请输入要发送的消息正文：");
        fgets(msg.mtext, msgsize, stdin);
        msg.mtext[strlen(msg.mtext) - 1] = '\0'; // 去掉换行符
        msgsnd(msgid, &msg, msgsize, 0);         // 将消息以阻塞的形式发送到消息队列中
        printf("消息存入成功\n");
        if (strcmp(msg.mtext, "quit") == 0 || strcmp(msg.mtext, "q") == 0)
        {
            printf("已停止发送\n\n");
            break;
        }
    }

    // 4.从消息队列里接受消息
    // 其他进程读取时候必须要用同样的key，即1步骤相同，而同样的key对应的msgid相同的
    struct msgbuf buf;
    while (1)
    {
        printf("请输入要接收的消息类型：");
        scanf("%ld", &buf.mtype);
        getchar();                                           // 吸收缓冲区中的换行符
        msgrcv(msgid, &buf, msgsize, buf.mtype, IPC_NOWAIT); // 非阻塞接受，即使不存在该消息类型也不会卡住
        if (strcmp(buf.mtext, "quit") == 0 || strcmp(buf.mtext, "q") == 0)
        {
            printf("已停止接受\n");
            break;
        }
        printf("接收成功，消息类型为：%ld，消息正文为：%s\n", buf.mtype, buf.mtext);
    }

    // 5.删除消息队列
    if (msgctl(msgid, IPC_RMID, NULL) == -1)
    {
        perror("msgctl error");
        return -1;
    }
    return 0;
}